package de.fmaul.common.io;

import java.io.File;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;

import com.google.common.base.Predicate;

/**
 * Implements an {@link Iterator<File>} that runs the recursive search that has
 * been defined by the {@link FileFinder} builder object.
 * 
 * @author Florian Maul
 */
public class FileFinderInterator implements Iterator<File> {

	/**
	 * The {@link Predicate} that defines the type of files that will be
	 * returned.
	 */
	private final Predicate<File> yieldFilter;

	/**
	 * The {@link Predicate} that is used for branching with recursion.
	 */
	private final Predicate<File> branchFilter;

	/**
	 * The {@link Predicate} that is used as filter to find specific files.
	 */
	private final Predicate<File> fileFilter;

	/**
	 * A stack that stores all files and directories that still habe to be
	 * processed.
	 */
	private LinkedList<File> fileStack = new LinkedList<File>();

	/**
	 * A queue to cache results that will be offered one by one by the
	 * {@link Iterator}.
	 */
	private Queue<File> resultQueue = new LinkedList<File>();

	/**
	 * Creates the Iterator with all configuration options for the search.
	 * 
	 * @param baseDir
	 *            The directory where the search will start.
	 * @param yieldFilter
	 *            The {@link Predicate} that defines the type of files that will
	 *            be returned.
	 * @param branchFilter
	 *            The {@link Predicate} that is used for branching with
	 *            recursion.
	 * @param fileFilter
	 *            The {@link Predicate} that is used as filter to find specific
	 *            files.
	 */
	public FileFinderInterator(File baseDir, Predicate<File> yieldFilter,
			Predicate<File> branchFilter, Predicate<File> fileFilter) {
		this.yieldFilter = yieldFilter;
		this.branchFilter = branchFilter;
		this.fileFilter = fileFilter;

		fileStack.addAll(Arrays.asList(baseDir.listFiles()));
	}

	/**
	 * Fills the result queue if necessary and tests if another result is
	 * available.
	 * 
	 * @see java.util.Iterator#hasNext()
	 */
	public boolean hasNext() {
		if (resultQueue.isEmpty()) {
			populateResults();
		}
		return !resultQueue.isEmpty();
	}

	/**
	 * Returns the next file from the result queue.
	 * 
	 * @see java.util.Iterator#next()
	 */
	public File next() {
		if (resultQueue.isEmpty()) {
			populateResults();
		}
		return resultQueue.poll();
	}

	/**
	 * Fills the result queue by processing the files and directories from
	 * fileStack.
	 */
	private void populateResults() {

		while (!fileStack.isEmpty() && resultQueue.isEmpty()) {
			File currentFile = fileStack.pop();

			if (yieldFilter.apply(currentFile) && fileFilter.apply(currentFile)) {
				resultQueue.offer(currentFile);
			}

			if (currentFile.isDirectory() && branchFilter.apply(currentFile)) {
				pushAllOnTop(fileStack, currentFile.listFiles());
			}
		}
	}

	/**
	 * Pushes an array of files to the top of a stack.
	 * 
	 * @param stack
	 *            The stack that is the target.
	 * @param files
	 *            An {@link Array} of {@link File}s.
	 */
	private void pushAllOnTop(LinkedList<File> stack, File[] files) {
		stack.addAll(0, Arrays.asList(files));
	}

	/**
	 * The remove method of the {@link Iterator} is not implemented.
	 * 
	 * @see java.util.Iterator#remove()
	 */
	public void remove() {
		// not implemented
	}
}
